package com.obj.commpont.common.utils.date;


import com.obj.commpont.common.utils.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 日期操作工具类
 *
 * @author liujia 2018/3/28 18:09
 */
public class DateUtils {
    private DateUtils() {
    }

    private static final Logger LOGGER = LogManager.getLogger(DateUtils.class);

    private static ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

    private static ThreadLocal<DateFormat> DATE_TIME_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    private static ThreadLocal<DateFormat> DATE_HOUR_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH"));

    private static ThreadLocal<DateFormat> DATE_TIME_SECOND_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmss"));

    private static ThreadLocal<DateFormat> DATE_TIME_SECONDMS_FORMAT_THREAD_LOCAL = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmmsssss"));

    //解析列表，根据配置的解析格式进行尝试解析，如果能解析则直接返回
    private static List<ThreadLocal<DateFormat>> FORMAT_LIST = new ArrayList<>();

    static {
        FORMAT_LIST.add(DATE_TIME_FORMAT_THREAD_LOCAL);
        FORMAT_LIST.add(DATE_FORMAT_THREAD_LOCAL);
        FORMAT_LIST.add(DATE_TIME_SECONDMS_FORMAT_THREAD_LOCAL);
        FORMAT_LIST.add(DATE_TIME_SECOND_FORMAT_THREAD_LOCAL);
        FORMAT_LIST.add(DATE_HOUR_FORMAT_THREAD_LOCAL);
    }

    /**
     * 一周的毫秒数
     */
    public static final long WEEK_MILLISECOND = 7 * 24 * 60 * 60 * 1000;

    /**
     * 一天的毫秒数
     */
    public static final long DAY_MILLISECOND = 24 * 60 * 60 * 1000;

    /**
     * 一小时的毫秒数
     */
    public static final long HOUR_MILLISECOND = 60 * 60 * 1000;

    /**
     * 一分钟的毫秒数
     */
    public static final long MINUTE_MILLISECOND = 60 * 1000;

    private static final String ONE_SECOND_AGO = "几秒前";
    private static final String ONE_MINUTE_AGO = "分钟前";
    private static final String ONE_HOUR_AGO = "小时前";
    private static final String YESTERDAY = "昨天";
    private static final String ONE_DAY_AGO = "天前";
    private static final String ONE_WEEK_AGO = "周前";
    private static final String ONE_MONTH_AGO = "月前";
    private static final String ONE_YEAR_AGO = "年前";

    /**
     * 将日期格式化为日期字符串
     *
     * @param date 日期,如果参数为null将返回null
     * @return 日期类型的字符串，不包含时间
     */
    public static String formatDate(Date date) {
        if (null == date) {
            return null;
        }
        return DATE_FORMAT_THREAD_LOCAL.get().format(date);
    }

    /**
     * 根据设定的任意格式进行日期格式化
     *
     * @param source       原始日期类型
     * @param formatPatter 格式化字符串
     * @return 格式化结果，如果source为null则返回空字符串
     */
    public static String formatAtWill(Date source, String formatPatter) {
        if (null == source) {
            return StringUtils.EMPTY;
        }
        SimpleDateFormat format = new SimpleDateFormat(formatPatter);
        return format.format(source);
    }

    /**
     * 根据支持的所有日期格式将日期转换为字符串
     *
     * @param date 日期，如果为null则返回null
     * @return 设定格式的字符串
     */
    public static String formatDateNonStrict(Date date) {
        String result = null;
        if (null == date) {
            return null;
        }

        for (ThreadLocal<DateFormat> supportFormatLocal : FORMAT_LIST) {
            result = supportFormatLocal.get().format(date);

            if (StringUtils.isNotBlank(result)) {
                return result;
            }
        }

        return result;
    }

    /**
     * 将日期格式化为时间戳字符串
     *
     * @param date 日期,如果参数为null将返回null
     * @return 日期类型的字符串，包含时间
     */
    public static String formatDateTime(Date date) {
        if (null == date) {
            return null;
        }
        return DATE_TIME_FORMAT_THREAD_LOCAL.get().format(date);
    }

    /**
     * 将日期格式化为时间戳字符串
     *
     * @param date 日期,如果参数为null将返回null
     * @return 日期类型的字符串，只精确到小时
     */
    public static String formatDateHour(Date date) {
        if (null == date) {
            return null;
        }
        return DATE_HOUR_FORMAT_THREAD_LOCAL.get().format(date);
    }

    /**
     * 获取的时间字符串，精确到毫秒
     *
     * @return 返回时间戳
     */
    public static String getMilliSecondDate() {
        return DATE_TIME_SECONDMS_FORMAT_THREAD_LOCAL.get().format(System.currentTimeMillis());

    }

    /**
     * 将字符串解析为日期对象
     *
     * @param source 源字符串,源字符串为空时返回null
     * @return 日期对象
     */
    public static Date parseDate(String source) {
        if (StringUtils.isBlank(source)) {
            return null;
        }

        try {
            return DATE_FORMAT_THREAD_LOCAL.get().parse(source);
        } catch (ParseException e) {
            LOGGER.info(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 采用不严格的格式进行日期格式化，会尝试所有注册的解析格式，如果匹配则进行返回，最后还会尝试unix时间戳的方式
     * 注意：注册的解析格式严格的在线，不然会出现精度丢失的问题
     *
     * @param source 原始字符串，如果为空则返回null
     * @return 解析后的日期，如果无法解析则返回null
     */
    public static Date parseDateNonStrict(String source) {
        Date result = null;
        if (null == source) {
            return null;
        }

        for (ThreadLocal<DateFormat> supportFormatLocal : FORMAT_LIST) {
            try {
                result = supportFormatLocal.get().parse(source);
                if (null != result) {
                    return result;
                }
            } catch (ParseException e) {
                result = null;
            }
        }

        if (null == result) {
            result = new Date(NumberUtils.parseToLong(source));
        }

        return null;
    }

    /**
     * 将字符串解析为日期对象
     *
     * @param source 源字符串,源字符串为空时返回null
     * @return 日期对象
     */
    public static Date parseDateTime(String source) {
        if (StringUtils.isBlank(source)) {
            return null;
        }

        try {
            return DATE_TIME_FORMAT_THREAD_LOCAL.get().parse(source);
        } catch (ParseException e) {
            LOGGER.info(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 将源字符串解析为日期对象，根据支持的日期格式进行解析，如日期类型不可解析时返回null<br/>
     * 格式不明确的场景调用此方法，格式明确时调用{@link #parseDateTime(String)}或{@link #parseDate(String)}
     *
     * @param source 源字符串,源字符串为空时返回null
     * @return 日期对象
     */
    public static Date parse(String source) {
        if (StringUtils.isBlank(source)) {
            return null;
        }

        Date parseResult;

        try {
            parseResult = DATE_TIME_FORMAT_THREAD_LOCAL.get().parse(source);
        } catch (ParseException e) {
            parseResult = null;
        }
        try {
            if (null == parseResult) {
                parseResult = DATE_FORMAT_THREAD_LOCAL.get().parse(source);
            }
        } catch (ParseException e) {
            parseResult = null;
        }

        if (null == parseResult) {
            LOGGER.info("不支持该格式的日期转换，源数据为[{}]", source);
        }

        return parseResult;
    }

    /**
     * 获取当前日期的日期格式字符串
     *
     * @return 日期格式字符串
     */
    public static String getCurrentDate() {
        return formatDate(new Date());
    }

    /**
     * 获取当前时间的时间格式字符串
     *
     * @return 时间格式字符串
     */
    public static String getCurrentDateTime() {
        return formatDateTime(new Date());
    }

    /**
     * 获取去除了时分秒毫秒的日期
     *
     * @param date 日期,如果传递null则会返回当前日期
     * @return 格式化后的日期
     */
    public static Date getClearDate(Date date) {
        if (null == date) {
            date = new Date();
        }
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        clearTimePart(calendar);
        return calendar.getTime();
    }

    /**
     * 指定时间是否在当前时间之前
     *
     * @param when 时间点
     * @return true表示参数时间在当前时间之前
     */
    public static boolean before(Date when) {
        return after(new Date(), when);
    }

    /**
     * 指定时间是否在当前时间之后
     *
     * @param when 时间点
     * @return true表示参数时间在当前时间之后
     */
    public static boolean after(Date when) {
        return after(when, new Date());
    }

    /**
     * 比较两个日期，判断<code>source</code>是否在<code>target</code>之前
     *
     * @param source 源时间
     * @param target 比较的目标时间
     * @return true表示source在target之前，false反之
     */
    public static boolean before(Date source, Date target) {
        return after(target, source);
    }

    /**
     * 比较两个日期，判断<code>source</code>是否在<code>target</code>之后
     *
     * @param source 源时间
     * @param target 比较的目标时间
     * @return true表示source在target之后，false反之
     */
    public static boolean after(Date source, Date target) {
        if (null == source || null == target) {
            return false;
        }
        Calendar sourceCalendar = Calendar.getInstance();
        sourceCalendar.setTime(source);

        return source.after(target);
    }

    /**
     * 日期中添加对应字段的值，支持负数
     *
     * @param date   原始日期
     * @param field  修改的字符类型，取值见{@link Calendar}
     * @param amount 修改的值，支持负数
     * @return 修改后的日期
     */
    public static Date addField(Date date, int field, int amount) {
        Calendar sourceCalendar = Calendar.getInstance();
        sourceCalendar.setTime(date);
        sourceCalendar.add(field, amount);
        return sourceCalendar.getTime();
    }

    /**
     * 日期中获取对应字段的值
     *
     * @param date  原始日期
     * @param field 修改的字符类型，取值见{@link Calendar}
     * @return 修改后的日期
     */
    public static int getField(Date date, int field) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(field);
    }

    /**
     * 日期中设置对应字段的值，支持负数
     *
     * @param date   原始日期
     * @param field  设置的字符类型，取值见{@link Calendar}
     * @param amount 设置的值
     * @return 设置后的日期
     */
    public static Date setField(Date date, int field, int amount) {
        Calendar sourceCalendar = Calendar.getInstance();
        sourceCalendar.setTime(date);
        sourceCalendar.set(field, amount);
        return sourceCalendar.getTime();
    }

    /**
     * 计算2个日期值之间相差的月份，不论天数
     *
     * @param source 起始日期,不能为空，否则返回0
     * @param target 截止日期,不能为空，否则返回0
     * @return 相差月份
     */
    public static int getDiffMonths(Date source, Date target) {
        if (null == source || null == target) {
            return 0;
        }
        Calendar sourceCalendar = Calendar.getInstance();
        sourceCalendar.setTime(source);
        Calendar targetCalendar = Calendar.getInstance();
        targetCalendar.setTime(target);

        int sourceYear = sourceCalendar.get(Calendar.YEAR);
        int sourceMonth = sourceCalendar.get(Calendar.MONTH);

        int targetYear = targetCalendar.get(Calendar.YEAR);
        int targetMonth = targetCalendar.get(Calendar.MONTH);

        int diffMonth = (sourceYear * 12 + sourceMonth) - (targetYear * 12 + targetMonth);
        return Math.abs(diffMonth);
    }

    /**
     * 计算2个日期值之间相差的天数
     *
     * @param source 起始日期,不能为空，否则返回0
     * @param target 截止日期,不能为空，否则返回0
     * @return 相差天数
     */
    public static int getDiffDays(Date source, Date target) {
        if (null == source || null == target) {
            return 0;
        }
        long diffSeconed = source.getTime() - target.getTime();
        diffSeconed = Math.abs(diffSeconed);
        return (int) (diffSeconed / DAY_MILLISECOND);
    }

    /**
     * 计算2个日期之间相差的小时数
     *
     * @param source 起始日期,不能为空，否则返回0
     * @param target 截止日期,不能为空，否则返回0
     * @return 相差小时
     */
    public static long getDiffHours(Date source, Date target) {
        if (null == source || null == target) {
            return 0;
        }
        long diffSeconed = source.getTime() - target.getTime();
        diffSeconed = Math.abs(diffSeconed);
        return Math.abs((diffSeconed / HOUR_MILLISECOND));
    }

    /**
     * 计算2个日期相差的分钟数
     *
     * @param source 起始日期,不能为空，否则返回0
     * @param target 截止日期,不能为空，否则返回0
     * @return 相差分钟数的绝对值
     */
    public static long getDiffMinutes(Date source, Date target) {
        return getDiffMinutes(source, target, true);
    }

    /**
     * 计算2个日期相差的分钟数
     *
     * @param source 起始日期,不能为空，否则返回0
     * @param target 截止日期,不能为空，否则返回0
     * @param abs    true表示返回绝对值
     * @return 相差的分钟数
     */
    public static long getDiffMinutes(Date source, Date target, boolean abs) {
        if (null == source || null == target) {
            return 0;
        }
        long diffSeconed = source.getTime() - target.getTime();
        if (abs) {
            diffSeconed = Math.abs(diffSeconed);
        }
        return (diffSeconed / MINUTE_MILLISECOND);
    }

    /**
     * 计算两个日期相差的秒数
     *
     * @param source 起始日期
     * @param target 截止日期
     * @param abs    是否返回绝对值
     * @return 相差的秒数，如果任意参数为空返回0
     */
    public static long getDiffSeconed(Date source, Date target, boolean abs) {
        if (null == source || null == target) {
            return 0;
        }
        long diffSeconed = (source.getTime() - target.getTime()) / 1000;
        if (abs) {
            return Math.abs(diffSeconed);
        }
        return diffSeconed;
    }

    /**
     * 获取2个日期之间相差的年，一般用于计算年年
     *
     * @param source 开始日期
     * @param target 结束日期
     * @return 相差的年数
     */
    public static int getDiffYears(Date source, Date target) {
        if (null == source || null == target) {
            return 0;
        }
        Calendar sourceCalendar = Calendar.getInstance();
        sourceCalendar.setTime(source);

        Calendar targetCalendar = Calendar.getInstance();
        targetCalendar.setTime(target);

        int diffYears = targetCalendar.get(Calendar.YEAR) - sourceCalendar.get(Calendar.YEAR);
        diffYears = Math.abs(diffYears);

        if (targetCalendar.get(Calendar.MONTH) < sourceCalendar.get(Calendar.MONTH)) {
            diffYears--;
        } else if (targetCalendar.get(Calendar.MONTH) == sourceCalendar.get(Calendar.MONTH)) {
            if (targetCalendar.get(Calendar.DAY_OF_MONTH) < sourceCalendar.get(Calendar.DAY_OF_MONTH)) {
                diffYears--;
            }
        }
        return diffYears;
    }

    /**
     * 将日期转换为传统的农历对象
     *
     * @param date 转换日期
     * @return 转换后的农历日期对象
     */
    public static Lunar getChineseLunar(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        return new Lunar(calendar);
    }

    /**
     * 获取当前时间对于的日期区间，具体支持的区间可查看{@link Interval}
     *
     * @param interval {@link Interval}
     * @return 区间映射，包含区间的起止时间
     */
    public static IntervalMap getInterval(Interval interval) {
        return getInterval(interval, new Date());
    }

    /**
     * 获取指定日期对应的日期区间，具体指出的区间可查看{@link Interval}
     *
     * @param interval {@link Interval}
     * @param date     指定的检索日期
     * @return 区间映射，包含区间的起止时间
     */
    public static IntervalMap getInterval(Interval interval, Date date) {
        Date startTime = null;
        Date endTime = null;

        if (null == interval || null == date) {
            return new IntervalMap(null, null);
        }

        Calendar startCalendar = Calendar.getInstance();
        Calendar endCalendar = Calendar.getInstance();

        int currentMonth, startMonth, endMonth;

        switch (interval) {
            case TODAY:
                startCalendar.setTime(date);

                endCalendar.setTime(date);
                endCalendar.add(Calendar.DAY_OF_YEAR, 1);

                break;
            case TOMORROW:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.DAY_OF_YEAR, 1);

                endCalendar.setTime(date);
                endCalendar.add(Calendar.DAY_OF_YEAR, 2);
                break;
            case YESTERDAY:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.DAY_OF_YEAR, -1);

                endCalendar.setTime(date);

                break;
            case THREE_DAY:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.DAY_OF_YEAR, -3);

                endCalendar.setTime(date);

                break;
            case FIVE_DAY:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.DAY_OF_YEAR, -5);

                endCalendar.setTime(date);

                break;
            case SEVEN_DAY:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.DAY_OF_YEAR, -7);

                endCalendar.setTime(date);

                break;
            case WEEK:
                startCalendar.setTime(date);

                boolean isSunday = startCalendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
                if (isSunday) {
                    startCalendar.add(Calendar.WEEK_OF_YEAR, -1);
                }
                startCalendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);

                endCalendar.setTime(date);
                if (!isSunday) {
                    endCalendar.add(Calendar.WEEK_OF_YEAR, 1);
                }
                endCalendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);

                break;
            case MONTH:
                startCalendar.setTime(date);
                startCalendar.set(Calendar.DAY_OF_MONTH, 1);

                endCalendar.setTime(date);
                endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMaximum(Calendar.DAY_OF_MONTH));

                break;
            case QUARTER:
                startCalendar.setTime(date);
                endCalendar.setTime(date);

                currentMonth = startCalendar.get(Calendar.MONTH);

                startMonth = (currentMonth % 3) * 3;
                endMonth = startMonth + 2;

                startCalendar.set(Calendar.MONTH, startMonth);
                startCalendar.set(Calendar.DAY_OF_MONTH, 1);

                endCalendar.set(Calendar.MONTH, endMonth);
                endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMaximum(Calendar.DAY_OF_MONTH));

                break;
            case HALY_YEAR:
                startCalendar.setTime(date);
                endCalendar.setTime(date);

                currentMonth = startCalendar.get(Calendar.MONTH);

                startMonth = (currentMonth % 6) * 6;
                endMonth = startMonth + 5;

                startCalendar.set(Calendar.MONTH, startMonth);
                startCalendar.set(Calendar.DAY_OF_MONTH, 1);

                endCalendar.set(Calendar.MONTH, endMonth);
                endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMaximum(Calendar.DAY_OF_MONTH));
                break;
            case YEAR:
                startCalendar.setTime(date);
                startCalendar.set(Calendar.MONTH, 0);
                startCalendar.set(Calendar.DAY_OF_MONTH, 1);

                endCalendar.setTime(date);
                endCalendar.set(Calendar.MONTH, 11);
                endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMaximum(Calendar.DAY_OF_MONTH));
                break;
            case LAST_YEAR:
                startCalendar.setTime(date);
                startCalendar.add(Calendar.YEAR, -1);
                startCalendar.set(Calendar.MONTH, 0);
                startCalendar.set(Calendar.DAY_OF_MONTH, 1);

                endCalendar.setTime(date);
                endCalendar.add(Calendar.YEAR, -1);
                endCalendar.set(Calendar.MONTH, 11);
                endCalendar.set(Calendar.DAY_OF_MONTH, endCalendar.getActualMaximum(Calendar.DAY_OF_MONTH));
                break;
            default:
                return new IntervalMap(startTime, endTime);
        }

        clearTimePart(startCalendar);
        clearTimePart(endCalendar);

        startTime = startCalendar.getTime();
        endTime = endCalendar.getTime();

        return new IntervalMap(startTime, endTime);
    }

    /**
     * 清理日期中的时分秒毫秒部分
     */
    private static void clearTimePart(Calendar calendar) {
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
    }

    /**
     * 获取日期的起止时间段枚举定义
     */
    public enum Interval {
        /**
         * 今日
         */
        TODAY,
        /**
         * 明日
         */
        TOMORROW,
        /**
         * 昨日
         */
        YESTERDAY,
        /**
         * 三天
         */
        THREE_DAY,
        /**
         * 五天
         */
        FIVE_DAY,
        /**
         * 七天
         */
        SEVEN_DAY,
        /**
         * 本周
         */
        WEEK,
        /**
         * 月
         */
        MONTH,
        /**
         * 季度
         */
        QUARTER,
        /**
         * 半年
         */
        HALY_YEAR,
        /**
         * 整年
         */
        YEAR,
        /**
         * 去年
         */
        LAST_YEAR
    }

    /**
     * 获取传入时间参数与当前时间差,使用一种比较自然的语言描述（eg:一分钟之前）
     *
     * @param date 要比较的时间
     * @return 返回结果字符串
     */
    public static String getTimeLag(Date date) {
        if (null == date) {
            return null;
        }
        //获取时间差
        long delta = new Date().getTime() - date.getTime();

        if (delta < MINUTE_MILLISECOND) {
            return ONE_SECOND_AGO;
        }
        if (delta < HOUR_MILLISECOND) {
            long minutes = delta / MINUTE_MILLISECOND;
            return (minutes <= 0 ? 1 : minutes) + ONE_MINUTE_AGO;
        }
        if (delta < DAY_MILLISECOND) {
            long hours = delta / HOUR_MILLISECOND;
            return (hours <= 0 ? 1 : hours) + ONE_HOUR_AGO;
        }
        if (delta < 2L * DAY_MILLISECOND) {
            return YESTERDAY;
        }
        if (delta < WEEK_MILLISECOND) {
            long days = delta / DAY_MILLISECOND;
            return (days <= 0 ? 1 : days) + ONE_DAY_AGO;
        }
        if (delta < 30L * DAY_MILLISECOND) {
            long days = delta / WEEK_MILLISECOND;
            return (days <= 0 ? 1 : days) + ONE_WEEK_AGO;
        }
        if (delta < 12L * 4L * WEEK_MILLISECOND) {
            long months = delta / (30L * DAY_MILLISECOND);
            return (months <= 0 ? 1 : months) + ONE_MONTH_AGO;
        } else {
            long years = delta / (365L * DAY_MILLISECOND);
            return (years <= 0 ? 1 : years) + ONE_YEAR_AGO;
        }
    }
}
